
package br.ufu.lsi.database.engine;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.util.HashSet;
import java.util.Set;

import twitter4j.IDs;
import twitter4j.JSONObject;
import twitter4j.Logger;
import br.ufu.lsi.database.model.Follow;
import br.ufu.lsi.database.model.User;
import br.ufu.lsi.util.FileUtil;
import br.ufu.lsi.util.JSONUtil;
import br.ufu.lsi.util.PropertiesUtil;

/**
 * TODO: treat exceptions
 * 
 * @author fabiola
 */
public class Collector {

    private static final String ROOT_DIR = PropertiesUtil.getProperty( "ROOT_DIR" );

    private static final String FOLLOWERS_DIR = PropertiesUtil.getProperty( "FOLLOWERS_DIR" );

    private static final String FRIENDS_DIR = PropertiesUtil.getProperty( "FRIENDS_DIR" );
    
    private static final int MAX_FANOUT = 10;

    private static final Logger log = Logger.getLogger( Collector.class );

    private TwitterAccess twitterAccess;

    public Collector( TwitterAccess twitterAccess ) {
        this.twitterAccess = twitterAccess;
    }
    
    

    /**
     * Building graph via breadth-first search
     * 
     * @param seed
     * @param screenName
     * @param twitter
     * @throws Exception
     */
    public void start( long seed, String screenName ) throws Exception {

        for ( int level = 0; level < 3; level++ ) {

            log.info( "[Level] " + level );

            if ( level == 0 ) {
                // get followers
                String filePath = ROOT_DIR + level + "/" + screenName;
                BufferedWriter bw = FileUtil.openOutputFile( filePath );
                writeNodeInfo( seed, false, bw );
                bw.close();

            } else if ( level == 1 ) {
                // get seeds
                evolveNetwork( ROOT_DIR, level );

            } else {
                // get followers files
                evolveNetwork( FOLLOWERS_DIR, level );

                // get friends files
                evolveNetwork( FRIENDS_DIR, level );
            }
        }
    }

    /**
     * For each file in current directory and level, for each line, get followers and friends
     * 
     * @param directory
     * @param level
     * @param twitter
     * @throws Exception
     */
    public void evolveNetwork( String directory, int level ) throws Exception {

        File[] listOfFiles = FileUtil.getListOfFiles( directory, level - 1 );

        // for each file
        for ( File file : listOfFiles ) {
            if ( ! file.getName().startsWith( "." ) ) {
                BufferedReader reader = FileUtil.openInputFile( file );
                String line;

                // for each userId in file
                while ( ( line = reader.readLine() ) != null ) {

                    // get userId followers
                    User user = JSONUtil.convertToUser( line );
                    String filePathWriterFollowers = FOLLOWERS_DIR + level + "/" + user.getId();
                    BufferedWriter bwFollowers = FileUtil.openOutputFile( filePathWriterFollowers );

                    Set< Follow > followers = user.getFollowers();
                    for ( Follow follower : followers ) {
                        User u = follower.getFollower();
                        try {

                            writeNodeInfo( u.getId(), true, bwFollowers );

                        } catch ( Exception e ) {

                            // that's ok. just ignore user
                            log.error( "Ignoring user..." );
                            e.printStackTrace();
                        }
                    }
                    bwFollowers.close();

                    // get userId friends
                    String filePathWriterFriends = FRIENDS_DIR + level + "/" + user.getId();
                    BufferedWriter bwFriends = FileUtil.openOutputFile( filePathWriterFriends );

                    Set< Follow > friends = user.getFriends();
                    for ( Follow friend : friends ) {
                        User u = friend.getFollowee();
                        try {

                            writeNodeInfo( u.getId(), true, bwFriends );

                        } catch ( Exception e ) {

                            // that's ok. just ignore user
                            log.error( "Ignoring user..." );
                            e.printStackTrace();
                        }
                    }
                    bwFriends.close();
                }
                reader.close();
            }
        }
    }

    /**
     * Write in file one JSON line wrt userId
     * 
     * @param userId
     * @param getFriends
     * @param bw
     * @param twitter
     * @throws Exception
     */
    public void writeNodeInfo( long userId, boolean getFriends, BufferedWriter bw ) throws Exception {

        JSONObject jo = new JSONObject();

        personalInfo( userId, jo );

        followersList( userId, jo );

        if ( getFriends )
            friendsList( userId, jo );
        else
            jo.put( "friends", new HashSet< Follow >() );

        jo.write( bw );
        bw.write( "\n" );
    }

    /**
     * Getting user id and screenName
     * 
     * @param twitter
     * @param userId
     * @param jo
     * @throws Exception
     */
    public void personalInfo( long userId, JSONObject jo ) throws Exception {
        
        twitter4j.User user = twitterAccess.getUserInfo( userId );
        
        jo.put( "id", user.getId() );
        jo.put( "screenName", user.getScreenName() );
        
        log.info( "getting info from [" + user.getId() + ", " + user.getScreenName() + "]" );
    }

    /**
     * Getting followers from userId
     * 
     * @param twitter
     * @param userId
     * @param jo
     * @throws Exception
     */
    public void followersList( long userId, JSONObject jo ) throws Exception {

        IDs followers;
        long cursor = - 1;
        boolean fanoutFlag = false;
        
        Set< Follow > followersRelations = new HashSet< Follow >();
        do {
            followers = twitterAccess.getFollowersIDs( userId, cursor );

            if ( followers == null ) {
                break;
            }

            log.info( "got " + followers.getIDs().length + " followers..." );

            for ( long id : followers.getIDs() ) {
                br.ufu.lsi.database.model.User followee = new br.ufu.lsi.database.model.User();
                br.ufu.lsi.database.model.User follower = new br.ufu.lsi.database.model.User();

                followee.setId( userId );
                follower.setId( id );

                Follow follow = new Follow();
                follow.setFollowee( followee );
                follow.setFollower( follower );
                follow.setInit( System.currentTimeMillis() );

                followersRelations.add( follow );
                
                if( followersRelations.size() > MAX_FANOUT ) {
                    log.info( "got max fanout of followers: " + followersRelations.size() );
                    fanoutFlag = true;
                    break;
                }
            }
            if( fanoutFlag )
                break;
        } while ( ( cursor = followers.getNextCursor() ) != 0 );
        jo.put( "followers", followersRelations );

    }

    /**
     * Getting userId followees
     * 
     * @param twitter
     * @param userId
     * @param jo
     * @throws Exception
     */
    public void friendsList( long userId, JSONObject jo ) throws Exception {

        IDs friends;
        long cursor = - 1;
        boolean fanoutFlag = false;
        
        Set< Follow > friendsRelations = new HashSet< Follow >();
        do {
            friends = twitterAccess.getFriendsIDs( userId, cursor );

            if ( friends == null ) {
                break;
            }

            log.info( "got " + friends.getIDs().length + " friends..." );

            for ( long id : friends.getIDs() ) {
                br.ufu.lsi.database.model.User followee = new br.ufu.lsi.database.model.User();
                br.ufu.lsi.database.model.User follower = new br.ufu.lsi.database.model.User();

                followee.setId( id );
                follower.setId( userId );

                Follow follow = new Follow();
                follow.setFollowee( followee );
                follow.setFollower( follower );

                follow.setInit( System.currentTimeMillis() );

                friendsRelations.add( follow );
                
                if( friendsRelations.size() > MAX_FANOUT ) {
                    log.info( "got max fanout of friends: " + friendsRelations.size() );
                    fanoutFlag = true;
                    break;
                }
            }

            if( fanoutFlag )
                break;
            
        } while ( ( cursor = friends.getNextCursor() ) != 0 );
        jo.put( "friends", friendsRelations );
    }

    

    

    
    
    public static void main( String ... args ) throws Exception {
        
        Long NYTSPORTS_ID = 1766701L;
        Collector collector = new Collector( new TwitterAccess() );
        //collector.start( NYTSPORTS_ID, "nytsports" );
        collector.start( 991826522L, "fabs177");
    }
}
